<?php
class MySQL extends MySQLi {
    private $connection = null;
    private $server = null;
    private $username = null;
    private $password = null;
    private $database = null;
    private $query_count = 0;
    private $query_count_without_fields = 0;
    private $cached_fields = array();
    
    function __construct($open_on_construct = true) {
        $this->server = DB_HOST;
        $this->username = DB_USERNAME;
        $this->password = DB_PASS;
        $this->database = DB_NAME;
        if($open_on_construct) {
            parent::__construct($this->server, $this->username, $this->password, $this->database);
            if(mysqli_connect_errno()) {
                $error = sprintf("Could not connect to MySQL server: %s", mysqli_connect_error());
                throw new Exception($error);
            }
            if(!parent::set_charset("utf8")) {
                $error = sprintf("Could not set default charset: %s", $this->error);
                throw new Exception($error);
            }
        }
        else {
            $this->connection = parent::init();
        }
    }
    
    /**
     * Generates SELECT query based on given parameters
     * @param string $table from which table to select data
     * @param array|string $variables which values to select. If an array is passed then selects all given values. If '*' is passed everything is selected
     * @param string $conditions optional conditions for WHERE. If null is passed then there will not be generated a WHERE clause.
     * @param array $joins optional describes all the joins for the given SELECT. Must be an array of arrays. Each of those must contain:
     * + type - what type of JOIN this is (for example "INNER" or "LEFT")
     * + on - condition for the join (for example "table1.value = table2.value")
     * + table - joinable table
     * @return ResultObject|null
     * @access public
     */
    public function select($table, $variables, $conditions = null, $joins = null, $order = null, $limit = null) {
        $sql = "SELECT ";
        
        if($variables != "*") {
            $sql .= implode($variables, ", ") . " ";
        }
        else {
            $sql .= "* ";
        }
        
        $sql .= "FROM $table";
        
        if($joins != null) {
            foreach($joins as $join) {
                $type = $join["type"];
                $on = $join["on"];
                $joinable_table = $join["table"];
                $sql .= " $type JOIN $joinable_table ON $on";
            }
        }
        
        if($conditions != null) {
            $sql .= " WHERE $conditions";
        }
        
        if($order != NULL) {
            $sql .= " ORDER BY $order";
        }
        
        if($limit != null) {
            $sql .= " LIMIT $limit";
        }
        
        $result = $this->query($sql);
        
        if($result === false) {
            $error = sprintf("Could not perform SELECT query: %s\n", $this->error);
            $error .= sprintf("Query: %s\n", $sql);
            throw new Exception($error);
            return null;
        }
        else {
            return $result;
        }
    }
    
    /**
     * Generetes and executes INSERT query based on given parameters
     * @param string $table in which table to insert data
     * @param array $variables contains information about fields in which the data must be inserted.
     * @param array $values new values to insert in table. If only an array is passed then inserts one row of data. If an array of arrays is passed then each of the arrays is inserted as a new row. All the values are automatically esacped.
     * @return int|bool returns newly inserted rows id or returns false if INSERT fails
     * @access public
     */
    public function insert($table, $variables, $values) {
        $sql = "INSERT INTO $table (" . implode($variables, ", ") . ") VALUES";
        if(is_array($values[0])) {
            $count = count($values) - 1;
            foreach($values as $key => $value) {
                $this->escape(&$value);
                $sql .= " ('" . implode($value, "', '") . "')";
                $sql .= ($count != $key) ? ", " : "";
            }
        }
        else {
            $this->escape(&$values);
            $sql .= " ('" . implode($values, "', '") . "')";
        }
        $result = $this->query($sql);
        
        if($result === true) {
            return $this->insert_id;
        }
        else {
            $error = sprintf("Could not perform INSERT query: %s<br />", $this->error);
            $error .= sprintf("Query: <b>%s</b>", $sql);
            throw new Exception($error);
            return null;
        }
    }
    
    public function update($table, $variables, $values, $options = array()) {
        $sql = "UPDATE $table SET ";
        
        $this->escape(&$values);
        foreach($values as $key => $value) {
            $pair = $variables[$key] . " = '$value', ";
            $sql .= $pair;
        }
        
        $sql = mb_substr($sql, 0, -2, "utf-8");
        
        if(!empty($options["where"])) {
            $sql .= "WHERE " . $options["where"];
        }
        
        $result = $this->query($sql);
        
        if($result === false) {
            $error = sprintf("Could not perform UPDATE query: %s<br />", $this->error);
            $error .= sprintf("Query: <b>%s</b>", $sql);
            throw new Exception($error);
            return false;
        }
        else {
            return true;
        }
    }
    
    public function delete($table, $where) {
        $sql = "DELETE FROM $table WHERE $where";
        $status = $this->query($sql);
        if($status) {
            return true;
        }
        else {
            $error = sprintf("Could not perform DELETE query: %s", $this->error);
            throw new Exception($error);
            return null;
        }
    }
    
    /*
        * Automatically escapes given parameters.
        * If an array is passed then only this array is escaped
        * If multiple values is passed then each of those are escaped - value can not be an array
    */
    public function escape() {
        $stack = debug_backtrace();
		if(isset($stack[0]["args"]))
		{
		    if(is_array($stack[0]["args"][0])) {
		        if($this->is_assoc($stack[0]["args"][0])) {
		          foreach($stack[0]["args"][0] as $key => $value) {
		            $stack[0]["args"][0][$key] = parent::escape_string($stack[0]["args"][0][$key]);
		          }
		        }
		        else {
		          $count = count($stack[0]["args"][0]);
  		        for($i = 0; $i < $count; $i++) {
  		            $stack[0]["args"][0][$i] = parent::escape_string($stack[0]["args"][0][$i]);
  		        }
		        }
		    }
		    else {
		        $count = count($stack[0]["args"]);
        		for($i = 0; $i < $count; $i++) {
        			$stack[0]["args"][$i] = parent::escape_string($stack[0]["args"][$i]);
        		}
		    }
		}
    }
    
    public function fields($table) {
        $table = strtolower($table);
        
        if($this->not_in_cache($table)) {
          $sql = "SHOW FIELDS FROM %s";
          $sql = sprintf($sql, $table);
          
          $result = $this->query($sql);
          
          if($result === false) {
              $error = "Could not perform SHOW FIELDS query: %s";
              $error = sprintf($error, $this->error);
              throw new Exception($error);
              return null;
          }
          else {
            $this->cached_fields[$table] = array();
            while($field = $result->fetch_row()) {
              $this->cached_fields[$table][] = $field[0];
            }
            return $this->cached_fields[$table];
          }
        }
        else {
          return $this->cached_fields[$table];
        }
    }
    
    public function count($table, $options = array()) {
      $sql = "SELECT COUNT(*) FROM $table";
      
      if(isset($options["where"])) {
        $sql .= " WHERE " . $options["where"];
      }
      
      $result = $this->query($sql);
      $row = $result->fetch_row();
      return $row[0];
    }
    
    private function not_in_cache($table) {
      return (!isset($this->cached_fields[$table]) or empty($this->cached_fields[$table])) ? true : false;
    }
    
    public function query($sql) {
      $this->inc_queries();
      $result = parent::query($sql);
      return $result;
    }
    
    public function is_assoc($array) {
      foreach(array_keys($array) as $k => $v) {
        if($k !== $v) {
          return true;
        }
      }
      return false;
    }
    
    private function inc_queries($from_fields = false) {
      $this->query_count++;
      if(!$from_fields) {
        $this->query_count_without_fields++;
      }
    }
    
    public function get_query_count($without_fields = false) {
      if($without_fields) {
        return $this->query_count_without_fields;
      }
      return $this->query_count;
    }
}
?>